"""
区域管理模块
处理自定义区域定义和进出判断逻辑
"""

import cv2
import numpy as np
from typing import List, Tuple, Optional, Dict
from dataclasses import dataclass
import json
import logging
from shapely.geometry import Point, Polygon

@dataclass
class RegionConfig:
    """区域配置数据类"""
    points: List[Tuple[int, int]]
    margin: Dict[str, int]  # {"top": 50, "bottom": 50, "left": 50, "right": 50}
    color: Tuple[int, int, int]  # BGR颜色
    thickness: int
    name: str = "detection_region"

class RegionManager:
    """区域管理器"""
    
    def __init__(self, config: RegionConfig = None):
        """
        初始化区域管理器
        
        Args:
            config: 区域配置
        """
        self.config = config
        self.region_polygon: Optional[Polygon] = None
        self.region_points: List[Tuple[int, int]] = []
        
        # 鼠标事件相关
        self.mouse_points: List[Tuple[int, int]] = []
        self.is_drawing = False
        self.temp_frame = None
        
        # 日志
        self.logger = logging.getLogger(__name__)
        
        if config and config.points:
            self.set_region_points(config.points)
    
    def set_region_points(self, points: List[Tuple[int, int]]):
        """
        设置区域点
        
        Args:
            points: 区域边界点列表
        """
        if len(points) < 3:
            self.logger.warning("区域点数量少于3个，无法形成有效区域")
            return False
        
        self.region_points = points.copy()
        
        try:
            # 创建Shapely多边形
            self.region_polygon = Polygon(points)
            if not self.region_polygon.is_valid:
                self.logger.warning("区域多边形无效")
                return False
            
            self.logger.info(f"区域设置成功，包含{len(points)}个点")
            return True
            
        except Exception as e:
            self.logger.error(f"设置区域失败: {e}")
            return False
    
    def is_point_in_region(self, point: Tuple[int, int]) -> bool:
        """
        判断点是否在区域内
        
        Args:
            point: 点坐标 (x, y)
            
        Returns:
            是否在区域内
        """
        if self.region_polygon is None:
            return False
        
        try:
            shapely_point = Point(point)
            return self.region_polygon.contains(shapely_point)
        except Exception as e:
            self.logger.error(f"点区域判断失败: {e}")
            return False
    
    def get_region_with_margin(self, frame_shape: Tuple[int, int]) -> Optional[Polygon]:
        """
        获取带边界的区域
        
        Args:
            frame_shape: 帧尺寸 (height, width)
            
        Returns:
            带边界的区域多边形
        """
        if not self.config or not self.config.margin:
            return self.region_polygon
        
        if not self.region_points:
            return None
        
        height, width = frame_shape
        margin = self.config.margin
        
        # 计算边界区域
        margin_points = []
        for x, y in self.region_points:
            # 应用边界偏移
            new_x = max(margin["left"], min(width - margin["right"], x))
            new_y = max(margin["top"], min(height - margin["bottom"], y))
            margin_points.append((new_x, new_y))
        
        try:
            return Polygon(margin_points)
        except Exception as e:
            self.logger.error(f"创建边界区域失败: {e}")
            return self.region_polygon
    
    def draw_region(self, frame: np.ndarray, with_margin: bool = False) -> np.ndarray:
        """
        在帧上绘制区域
        
        Args:
            frame: 输入帧
            with_margin: 是否绘制边界
            
        Returns:
            绘制区域后的帧
        """
        if not self.region_points:
            return frame
        
        annotated_frame = frame.copy()
        
        # 绘制主区域
        if len(self.region_points) >= 3:
            points = np.array(self.region_points, dtype=np.int32)
            
            # 只绘制边界线，不填充区域（移除半透明背景）
            cv2.polylines(
                annotated_frame, [points], True, 
                self.config.color if self.config else (0, 255, 0),
                self.config.thickness if self.config else 2
            )
            
            # 绘制顶点 (可选，如果需要更简洁的界面可以注释掉)
            # for point in self.region_points:
            #     cv2.circle(annotated_frame, point, 5, (0, 0, 255), -1)
        
        # 绘制边界区域
        if with_margin and self.config and self.config.margin:
            margin_polygon = self.get_region_with_margin(frame.shape[:2])
            if margin_polygon:
                margin_points = np.array(list(margin_polygon.exterior.coords)[:-1], dtype=np.int32)
                cv2.polylines(annotated_frame, [margin_points], True, (255, 0, 0), 1)
        
        return annotated_frame
    
    def mouse_callback(self, event, x, y, flags, param):
        """鼠标回调函数，用于交互式设置区域"""
        if event == cv2.EVENT_LBUTTONDOWN:
            self.mouse_points.append((x, y))
            self.logger.info(f"添加点: ({x}, {y})")
            
        elif event == cv2.EVENT_RBUTTONDOWN:
            if len(self.mouse_points) >= 3:
                self.set_region_points(self.mouse_points)
                self.logger.info("区域设置完成")
            else:
                self.logger.warning("需要至少3个点来定义区域")
        
        elif event == cv2.EVENT_MBUTTONDOWN:
            # 中键清除所有点
            self.mouse_points.clear()
            self.logger.info("清除所有点")
    
    def interactive_region_setup(self, frame: np.ndarray, window_name: str = "Region Setup") -> bool:
        """
        交互式区域设置
        
        Args:
            frame: 参考帧
            window_name: 窗口名称
            
        Returns:
            是否成功设置区域
        """
        self.temp_frame = frame.copy()
        self.mouse_points.clear()
        
        cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
        cv2.setMouseCallback(window_name, self.mouse_callback)
        
        self.logger.info("交互式区域设置:")
        self.logger.info("- 左键点击添加区域点")
        self.logger.info("- 右键点击完成区域设置")
        self.logger.info("- 中键点击清除所有点")
        self.logger.info("- 按ESC键退出")
        
        while True:
            display_frame = self.temp_frame.copy()
            
            # 绘制已选择的点
            for i, point in enumerate(self.mouse_points):
                cv2.circle(display_frame, point, 5, (0, 0, 255), -1)
                cv2.putText(
                    display_frame, str(i), 
                    (point[0] + 10, point[1] - 10),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2
                )
            
            # 绘制临时多边形
            if len(self.mouse_points) >= 3:
                points = np.array(self.mouse_points, dtype=np.int32)
                cv2.polylines(display_frame, [points], True, (0, 255, 0), 2)
            
            # 显示指令
            cv2.putText(
                display_frame, f"Points: {len(self.mouse_points)}", 
                (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2
            )
            cv2.putText(
                display_frame, "Left: Add point, Right: Finish, Middle: Clear, ESC: Exit", 
                (10, display_frame.shape[0] - 20), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2
            )
            
            cv2.imshow(window_name, display_frame)
            
            key = cv2.waitKey(1) & 0xFF
            if key == 27:  # ESC键
                break
            
            # 检查是否已设置区域
            if self.region_polygon is not None:
                break
        
        cv2.destroyWindow(window_name)
        return self.region_polygon is not None
    
    def save_region_config(self, file_path: str) -> bool:
        """
        保存区域配置到文件
        
        Args:
            file_path: 文件路径
            
        Returns:
            是否保存成功
        """
        if not self.region_points:
            self.logger.warning("没有区域点可保存")
            return False
        
        try:
            config_data = {
                "points": self.region_points,
                "margin": self.config.margin if self.config else {"top": 50, "bottom": 50, "left": 50, "right": 50},
                "color": self.config.color if self.config else [0, 255, 0],
                "thickness": self.config.thickness if self.config else 2,
                "name": self.config.name if self.config else "detection_region"
            }
            
            with open(file_path, 'w', encoding='utf-8') as f:
                json.dump(config_data, f, indent=2, ensure_ascii=False)
            
            self.logger.info(f"区域配置已保存到: {file_path}")
            return True
            
        except Exception as e:
            self.logger.error(f"保存区域配置失败: {e}")
            return False
    
    def load_region_config(self, file_path: str) -> bool:
        """
        从文件加载区域配置
        
        Args:
            file_path: 文件路径
            
        Returns:
            是否加载成功
        """
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                config_data = json.load(f)
            
            self.config = RegionConfig(
                points=config_data["points"],
                margin=config_data.get("margin", {"top": 50, "bottom": 50, "left": 50, "right": 50}),
                color=tuple(config_data.get("color", [0, 255, 0])),
                thickness=config_data.get("thickness", 2),
                name=config_data.get("name", "detection_region")
            )
            
            self.set_region_points(config_data["points"])
            self.logger.info(f"区域配置已从文件加载: {file_path}")
            return True
            
        except Exception as e:
            self.logger.error(f"加载区域配置失败: {e}")
            return False
    
    def get_region_info(self) -> Dict:
        """获取区域信息"""
        if not self.region_polygon:
            return {}
        
        bounds = self.region_polygon.bounds
        return {
            "points_count": len(self.region_points),
            "area": self.region_polygon.area,
            "bounds": {
                "min_x": bounds[0],
                "min_y": bounds[1],
                "max_x": bounds[2],
                "max_y": bounds[3]
            },
            "is_valid": self.region_polygon.is_valid
        }


if __name__ == "__main__":
    # 测试代码
    logging.basicConfig(level=logging.INFO)
    
    # 创建测试图像
    test_frame = np.zeros((600, 800, 3), dtype=np.uint8)
    test_frame[:] = (50, 50, 50)  # 深灰色背景
    
    # 创建区域管理器
    config = RegionConfig(
        points=[],
        margin={"top": 50, "bottom": 50, "left": 50, "right": 50},
        color=(0, 255, 0),
        thickness=2
    )
    
    region_manager = RegionManager(config)
    
    # 交互式设置区域
    success = region_manager.interactive_region_setup(test_frame)
    
    if success:
        print("区域设置成功！")
        print("区域信息:", region_manager.get_region_info())
        
        # 保存配置
        region_manager.save_region_config("test_region.json")
        
        # 测试点判断
        test_points = [(100, 100), (400, 300), (700, 500)]
        for point in test_points:
            in_region = region_manager.is_point_in_region(point)
            print(f"点 {point} 是否在区域内: {in_region}")
    else:
        print("区域设置失败或被取消") 